#include "game.h"

#include <iostream>
#include <exception>
#include <list>
#include <math.h>

using namespace std;

void Game::add(GameObject* o) {
	if(o->isDrawable()) {
		try {
			Drawable* pD = dynamic_cast<Drawable*>(o);
			if(pD != NULL) {
				addDrawable(pD);
			}
		} catch(exception e) {}
	}

	if(o->isUpdateable()) {
		try {
			Updateable* pU = dynamic_cast<Updateable*>(o);
			if(pU != NULL) {
				addUpdateable(pU);
			}
		} catch(exception e) {}
	}

	if(o->isPhysicsable()) {
		try {
			Physicsable* pP = dynamic_cast<Physicsable*>(o);
			if(pP != NULL) {
				addPhysicsable(pP);
			}
		} catch(exception e) {}
	}
}

void Game::addDrawable(Drawable* pD) {
	if(!drawables.empty()) {
		list<Drawable*>::iterator it = drawables.begin();
		while((it != drawables.end()) && (pD->getLayer() > (*it)->getLayer())) {
			it++;
		}
		if(it == drawables.end()) {
			drawables.push_back(pD);
		} else {
			drawables.insert(it, pD);
		}
	} else {
		drawables.push_front(pD);
	}
}

void Game::addUpdateable(Updateable* pU) {
	updateables.push_back(pU);
}

void Game::addPhysicsable(Physicsable* pP) {
	//Also have this function add special collision types
	
	cpSpaceAddBody(space_, pP->getBody());
	if(!(pP->getShapes()->empty())) {
		list<cpShape*>::iterator sIt;
	
		for(sIt = pP->getShapes()->begin(); sIt != pP->getShapes()->end(); sIt++) {
			cpSpaceAddShape(space_, *sIt);
		}

		cpResetShapeIdCounter();
	}
}

void Game::init() {
	//Set gravity... to nothing
    space_->gravity = cpvzero;
    
	//Set the space hash to size 4.0 and num 10000
    cpSpaceResizeActiveHash(space_, 4.0, 10000);
	
	//NOTE!!! Do not remove these, or face the wrath of null pointer dereferencing
	//Set up physicsable space
	Physicsable::setSpace(space_);

	//Set up spawnable queue
	Spawnable::setSpawnQueue(&spawnables);
	//END NOTE!!!

	add(&testCam_);
	add(&bg_);
	add(&test_);
    
    int numBlocks = 40;
	float blockRad = 1.0 / (float)numBlocks;
	float radius = 200.0;
	float blockSize = sin(blockRad) * radius * 5.8;
	float blockMass = 1000.0;
	cpVect rotator = cpvforangle(2 * PI / (1 / blockRad));
	cpVect blockPos = cpv(0.0, radius);
	cpVect direction;
	for(int i = 0; i < numBlocks; i++) {
		direction = cpvsub(cpvzero, blockPos);
		add(new Block(blockPos, direction, blockSize, blockSize, blockMass));
		blockPos = cpvrotate(blockPos, rotator);
	}
}

void Game::step() {
	//Run input	
	doInput();
    
    //Run updates
	updateStep();

	//Run spawns
	spawnStep();

	//Run physics
	physicsStep();

	//Run draw
	drawStep();
}

void Game::drawStep() {
	list<Drawable*>::iterator dIt;
	for(dIt = drawables.begin(); dIt != drawables.end(); dIt++) {
		(*dIt)->draw();
	}
}

void Game::updateStep() {
	testCam_.setPos(test_.getBody()->p);	
	
	//Find time step. NOTE, standardize this with update timestep
	float step = 2.0;
	list<Updateable*>::iterator uIt;
	for(uIt = updateables.begin(); uIt != updateables.end(); uIt++) {
		(*uIt)->update(step);
	}
}

void Game::physicsStep() {
	cpFloat dt = 0.5/60.0;
	cpSpaceStep(space_, dt);
}

void Game::spawnStep() {
	while(!spawnables.empty()) {
		add(spawnables.front());
		spawnables.pop();
	}
}

void Game::doInput() {
	ButtonPress button;
	queue<ButtonPress>* input = input_.getQueue();
	
	while(!(input->empty())) {
		button = input->front();
		input->pop();

		switch(button.getButton()) {
			case 'u':
				if(button.getDown()) {
					testCam_.moveForwards();
				} else {
					testCam_.stopMoveForwards();
				}
				break;

			case 'j':
				if(button.getDown()) {
					testCam_.moveBack();
				} else {
					testCam_.stopMoveBack();
				}
				break;

			case 'h':
				if(button.getDown()) {
					testCam_.moveLeft();
				} else {
					testCam_.stopMoveLeft();
				}
				break;

			case 'k':
				if(button.getDown()) {
					testCam_.moveRight();
				} else {
					testCam_.stopMoveRight();
				}
				break;

			case 'b':
				if(button.getDown()) {
					testCam_.zoomIn();
				} else {
					testCam_.stopZoomIn();
				}
				break;

			case 'n':
				if(button.getDown()) {
					testCam_.zoomOut();
				} else {
					testCam_.stopZoomOut();
				}
				break;

			default:
				test_.respond(button);
		}
	}
}

